#!/system/bin/sh

export PATH="/data/adb/magisk:/data/adb/ksu/bin:/data/adb/ap/bin:$PATH:/data/data/com.termux/files/usr/bin"

scripts=$(realpath $0)
scripts_dir=$(dirname ${scripts})

source ${scripts_dir}/box.config

mkdir -p ${run_path}
mkdir -p ${box_path}/${bin_name}

# ${box_path}/bin/yq -i ".tproxy-port=${tproxy_port}" ${box_path}/clash/config.yaml
# ${box_path}/bin/yq -i ".dns.listen=\"${clash_dns_listen}\"" ${box_path}/clash/config.yaml
# ${box_path}/bin/yq -i ".dns.fake-ip-range=\"${clash_fake_ip_range}\"" ${box_path}/clash/config.yaml

# ${box_path}/bin/yq -o=json -i "(.inbounds[] | select(.type == \"tproxy\") | .listen_port) = ${tproxy_port}" ${box_path}/sing-box/config.json

find ${box_path} -mtime +3 -type f -name "*.log" | xargs rm -f

log() {
  export TZ=Asia/Shanghai
  now=$(date +"[%Y-%m-%d %H:%M:%S %Z]")
  case $1 in
    Info)
      [ -t 1 ] && echo -e "\033[1;32m${now} [Info]: $2\033[0m" || echo "${now} [Info]: $2"
      ;;
    Warn)
      [ -t 1 ] && echo -e "\033[1;33m${now} [Warn]: $2\033[0m" || echo "${now} [Warn]: $2"
      ;;
    Error)
      [ -t 1 ] && echo -e "\033[1;31m${now} [Error]: $2\033[0m" || echo "${now} [Error]: $2"
      ;;
    *)
      [ -t 1 ] && echo -e "\033[1;30m${now} [$1]: $2\033[0m" || echo "${now} [$1]: $2"
      ;;
  esac
}

check_permission() {
  if which ${bin_name} | grep -q "/system/bin/" ; then
    box_user=$(echo ${box_user_group} | awk -F ':' '{print $1}')
    box_group=$(echo ${box_user_group} | awk -F ':' '{print $2}')
    box_user_id=$(id -u ${box_user})
    box_group_id=$(id -g ${box_group})
    [ ${box_user_id} ] && [ ${box_group_id} ] || \
    (box_user_group="root:net_admin" && log Error "${box_user_group} error, use root:net_admin instead.")
    bin_path=$(which ${bin_name})
    chown ${box_user_group} ${bin_path}
    chmod 0755 ${bin_path}
    if [ "${box_user_id}" != "0" ] || [ "${box_group_id}" != "3005" ] ; then
      # setcap has been deprecated as it does not support binary outside of the /system/bin directory
      setcap 'cap_net_admin,cap_net_raw,cap_net_bind_service,cap_sys_ptrace,cap_dac_read_search+ep' ${bin_path} || \
      (box_user_group="root:net_admin" && log Error "setcap authorization failed, you may need libcap package.")
    fi
    chown -R ${box_user_group} ${box_path}
    return 0
  elif [ -f ${bin_path} ] ; then
    box_user_group="root:net_admin"
    chown ${box_user_group} ${bin_path}
    chmod 0700 ${bin_path}
    chown -R ${box_user_group} ${box_path}
    return 0
  else
    return 1
  fi
}

start_bin() {
  ulimit -SHn 1000000
  case "${bin_name}" in
    sing-box)
      if ${bin_path} check -D ${box_path}/${bin_name} > ${run_path}/check.log 2>&1 ; then
        log Info "starting ${bin_name} service."
        nohup busybox setuidgid ${box_user_group} ${bin_path} run -D ${box_path}/${bin_name} > /dev/null 2> ${run_path}/error_${bin_name}.log &
        echo -n $! > ${pid_file}
        return 0
      else
        log Error "configuration check failed, please check the ${run_path}/check.log file."
        return 1
      fi
      ;;
    clash|mihomo)
      if ${bin_path} -t -d ${box_path}/${bin_name} > ${run_path}/check.log 2>&1 ; then
        log Info "starting ${bin_name} service."
        nohup busybox setuidgid ${box_user_group} ${bin_path} -d ${box_path}/${bin_name} > ${box_path}/${bin_name}/${bin_name}_$(date +%Y%m%d%H%M).log 2> ${run_path}/error_${bin_name}.log &
        echo -n $! > ${pid_file}
        return 0
      else
        log Error "configuration check failed, please check the ${run_path}/check.log file."
        return 1
      fi
      ;;
    xray)
      export XRAY_LOCATION_ASSET="${box_path}/${bin_name}"
      if ${bin_path} -test -confdir ${box_path}/${bin_name} > ${run_path}/check.log 2>&1 ; then
        log Info "starting ${bin_name} service."
        nohup busybox setuidgid ${box_user_group} ${bin_path} -confdir ${box_path}/${bin_name} > /dev/null 2> ${run_path}/error_${bin_name}.log &
        echo -n $! > ${pid_file}
        return 0
      else
        log Error "configuration check failed, please check the ${run_path}/check.log file."
        return 1
      fi
      ;;
    v2ray)
      export V2RAY_LOCATION_ASSET="${box_path}/${bin_name}"
      if ${bin_path} test -d ${box_path}/${bin_name} > ${run_path}/check.log 2>&1 ; then
        log Info "starting ${bin_name} service."
        nohup busybox setuidgid ${box_user_group} ${bin_path} run -d ${box_path}/${bin_name} > /dev/null 2> ${run_path}/error_${bin_name}.log &
        echo -n $! > ${pid_file}
        return 0
      else
        log Error "configuration check failed, please check the ${run_path}/check.log file."
        return 1
      fi
      ;;
    hysteria)
      log Info "starting ${bin_name} service."
      nohup busybox setuidgid ${box_user_group} ${bin_path} -c ${box_path}/${bin_name}/config.yaml > ${box_path}/${bin_name}/${bin_name}_$(date +%Y%m%d%H%M).log 2> ${run_path}/error_${bin_name}.log &
      echo -n $! > ${pid_file}
      return 0
      ;;
    *)
      log Error "$1 core error, it must be one of ${bin_name_list[*]}"
      return 2
      ;;
  esac
}

find_netstat_path() {
  [ -f /system/bin/netstat ] && alias netstat="/system/bin/netstat" && return 0
  [ -f /system/xbin/netstat ] && alias netstat="/system/xbin/netstat" && return 0
  return 1
}

wait_bin_listen() {
  wait_count=0
  bin_pid=$(busybox pidof ${bin_name})
  find_netstat_path && \
  check_bin_cmd="netstat -tnulp | grep -q ${bin_name}" || \
  check_bin_cmd="ls -lh /proc/${bin_pid}/fd | grep -q socket"
  while [ ${bin_pid} ] && ! eval "${check_bin_cmd}" && [ ${wait_count} -lt 100 ] ; do
    sleep 1 ; wait_count=$((${wait_count} + 1))
  done
  if [ ${bin_pid} ] && eval "${check_bin_cmd}" ; then
    return 0
  else
    return 1
  fi
}

display_bin_status() {
  if bin_pid=$(busybox pidof ${bin_name}) ; then
    log Info "${bin_name} has started with the $(stat -c %U:%G /proc/${bin_pid}) user group."
    log Info "${bin_name} service is running. ( PID: ${bin_pid} )"
    log Info "${bin_name} memory usage: $(cat /proc/${bin_pid}/status | grep -w VmRSS | awk '{print $2$3}')"
    log Info "${bin_name} cpu usage: $((/system/bin/ps -eo %CPU,NAME | grep ${bin_name} | awk '{print $1"%"}') 2> /dev/null || dumpsys cpuinfo | grep ${bin_name} | awk '{print $1}')"
    log Info "${bin_name} running time: $(busybox ps -o comm,etime | grep ${bin_name} | awk '{print $2}')"
    echo -n ${bin_pid} > ${pid_file}
    return 0
  else
    log Warn "${bin_name} service is stopped."
    return 1
  fi
}

start_service() {
  if check_permission ; then
    log Info "${bin_name} will be started with the ${box_user_group} user group."
    if start_bin && wait_bin_listen ; then
      log Info "${bin_name} service is running. ( PID: $(cat ${pid_file}) )"
      return 0
    else
      if bin_pid=$(pidof ${bin_name}) ; then
        log Warn "${bin_name} service is running but may not listening. ( PID: ${bin_pid} )"
        return 0
      else
        log Error "start ${bin_name} service failed, please check the ${run_path}/error_${bin_name}.log file."
        rm -f ${pid_file} >> /dev/null 2>&1
        return 1
      fi
    fi
  else
    log Error "missing ${bin_name} core, please download and place it in the ${box_path}/bin/ directory"
    return 2
  fi
}

stop_service() {
  if display_bin_status ; then
    log Warn "stopping ${bin_name} service."
    kill $(cat ${pid_file}) || killall ${bin_name}
    sleep 1
    display_bin_status
  fi
  rm -f ${pid_file} >> /dev/null 2>&1
}

# create_tun_link() {
#   mkdir -p /dev/net
#   [ ! -L /dev/net/tun ] && ln -s /dev/tun /dev/net/tun
# }

probe_tun_device() {
  ifconfig | grep -q ${tun_device} || return 1
}

probe_tun_index() {
  while [ ! -f "/data/misc/net/rt_tables" ] ; do
    sleep 3
  done
  cat /data/misc/net/rt_tables | while read -r index name ; do
    if [ ${name} = ${tun_device} ] ; then
      tun_table_index=${index}
      return 0
    fi
  done
  return 1
}

tun_forward_ip_rules() {
  ip rule $1 iif lo goto 6000 pref 5000
  ip rule $1 iif ${tun_device} lookup main suppress_prefixlength 0 pref 5010
  ip rule $1 iif ${tun_device} goto 6000 pref 5020
  ip rule $1 from 10.0.0.0/8 lookup ${tun_table_index} pref 5030
  ip rule $1 from 172.16.0.0/12 lookup ${tun_table_index} pref 5040
  ip rule $1 from 192.168.0.0/16 lookup ${tun_table_index} pref 5050
  ip rule $1 nop pref 6000
}

tun_forward_ip_rules_del() {
  ip rule del pref 5000
  ip rule del pref 5010
  ip rule del pref 5020
  ip rule del pref 5030
  ip rule del pref 5040
  ip rule del pref 5050
  ip rule del pref 6000
}

sing_tun_ip_rules() {
  ip rule $1 from all iif ${tun_device} lookup main suppress_prefixlength 0 pref 8000
  ip rule $1 lookup main pref 7000
}

tun_forward_iptables_rules() {
  # iptables $1 FORWARD -s 10.0.0.0/8 -o ${tun_device} -j ACCEPT
  # iptables $1 FORWARD -s 172.16.0.0/12 -o ${tun_device} -j ACCEPT
  # iptables $1 FORWARD -s 192.168.0.0/16 -o ${tun_device} -j ACCEPT
  iptables -w 100 $1 FORWARD -o ${tun_device} -j ACCEPT
  iptables -w 100 $1 FORWARD -i ${tun_device} -j ACCEPT
  ip6tables -w 100 $1 FORWARD -o ${tun_device} -j ACCEPT
  ip6tables -w 100 $1 FORWARD -i ${tun_device} -j ACCEPT
  # iptables $1 PREROUTING -t nat ! -i ${tun_device} -s 10.0.0.0/8 -p udp --dport 53 -j DNAT --to 1.1.1.1
  # iptables $1 PREROUTING -t nat ! -i ${tun_device} -s 172.16.0.0/12 -p udp --dport 53 -j DNAT --to 1.1.1.1
  # iptables $1 PREROUTING -t nat ! -i ${tun_device} -s 192.168.0.0/16 -p udp --dport 53 -j DNAT --to 1.1.1.1
  ## iptables $1 PREROUTING -t nat ! -i ${tun_device} -p udp --dport 53 -j DNAT --to 1.1.1.1
  ## ip6tables -w 100 $1 FORWARD -j REJECT --reject-with icmp6-no-route
}

tun_forward_enable() {
  tun_forward_disable
  sleep 1
  echo 1 > /proc/sys/net/ipv4/ip_forward
  echo 2 > /proc/sys/net/ipv4/conf/default/rp_filter
  echo 2 > /proc/sys/net/ipv4/conf/all/rp_filter
  ## echo 0 > /dev/ip_forward_stub
  ## chown $(stat -c '%u:%g' /data/misc/net/rt_tables) /dev/ip_forward_stub
  ## chcon $(stat -Z -c '%C' /data/misc/net/rt_tables) /dev/ip_forward_stub
  ## mount -o bind /dev/ip_forward_stub /proc/sys/net/ipv4/ip_forward

  # create_tun_link
  probe_tun_index && tun_forward_ip_rules "add"
  probe_tun_device && tun_forward_iptables_rules "-I" && sing_tun_ip_rules "add" && log Info "tun hotspot support is enabled."
  return 0
}

tun_forward_disable() {
  # tun_forward_ip_rules "del" >> /dev/null 2>&1
  sing_tun_ip_rules "del" >> /dev/null 2>&1
  tun_forward_ip_rules_del >> /dev/null 2>&1
  tun_forward_iptables_rules "-D" >> /dev/null 2>&1
  log Warn "tun hotspot support is disabled."
}

case "$1" in
  start)
    display_bin_status || start_service
    [ $? = "0" ] && tun_forward_enable
    ;;
  stop)
    stop_service
    tun_forward_disable
    ;;
  restart)
    stop_service
    sleep 2
    start_service
    ;;
  status)
    display_bin_status
    ;;
  *)
    log Error "$0 $1 usage: $0 {start|stop|restart|status}"
    ;;
esac
